/*
 * @file iso9660fs.h
 *
 *  Application interface to ISO9660 filesystem.
 *
 */

#ifndef __ISO9660FS_H_
#define __ISO9660FS_H_

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/bdbuf.h>

#include "iso9660.h"
#include "iso9660_types.h"


#ifdef __cplusplus
extern "C" {
#endif

/**
 *  ISO9660 descriptor types
 */
typedef enum {
  ISO9660_BOOT_RECORD_DESCRIPTOR = 0,       /**< Boot record descriptor */
  ISO9660_PRIMARY_VOLUME_DESCRIPTOR,        /**< Primary volume descriptor */
  ISO9660_SUPPLEMENTARY_VOLUME_DESCRIPTOR,  /**< Supplementary volume descriptor */
  ISO9660_PARTITION_DESCRIPTOR,             /**< Partition volume descriptor */
  ISO9660_ENHANCED_VOLUME_DESCRIPTOR,       /**< ISO9660:1999 addition, it's a supp. desc.
                                                 with a specific version field */
  ISO9660_SET_TERMINATOR_DESCRIPTOR = 255   /**< Volume set terminator descriptor */
} iso9660_descriptor_type;


/**
 *  Bi-endian 16-bit integer
 *
 *  wx yz is stored as yz wx wx yz
 */
typedef struct iso9660_numeric16_s {
  uint16_t      le; /**< Little-endian representation */
  uint16_t      be; /**< Big-endian representation */
} __attribute__((__packed__)) iso9660_numeric16_t;



/**
 *  Bi-endian 32-bit integer
 *
 *  ab cd wx yz is stored as yz wx cd ab ab cd wx yz
 */
typedef struct iso9660_numeric32_s {
  uint32_t      le; /**< Little-endian representation */
  uint32_t      be; /**< Big-endian representation */
} __attribute__((__packed__)) iso9660_numeric32_t;


/**
 *  ISO9660 BCD date and time representation used
 * in descriptors.
 *
 *  Each field is stored as BCD digit
 *  e.g. 2011 is stored as '2' '0' '1' '1'
 */
typedef struct iso9660_bcd_date_s {
  char          year[4];      /**< Year (4-digit BCD: 1 to 9999) */
  char          month[2];     /**< Month (2-digit BCD: 1 to 12) */
  char          day[2];       /**< Day (2-digit BCD: 1 to 31) */
  char          hour[2];      /**< Hour (2-digit BCD: 0 to 23) */
  char          minute[2];    /**< Minute (2-digit BCD: 0 to 59) */
  char          second[2];    /**< Second (2-digit BCD: 0 to 59) */
  char          h_second[2];  /**< Hundredth of second */
  int8_t        gmt_offset;   /**< Offset related to GMT, by 15-min interval (from -48 to +52) */
} __attribute__((__packed__)) iso9660_bcd_date_t;


/**
 *  ISO9660 date and time representation used in records.
 */
typedef struct iso9660_date_s {
  uint8_t        year;        /**< Number of year since 1900 */
  uint8_t        month;       /**< Month (1 to 12) */
  uint8_t        day;         /**< Day (1 to 31) */
  uint8_t        hour;        /**< Hour (0 to 23) */
  uint8_t        minute;      /**< Minute (0 to 59) */
  uint8_t        second;      /**< Second (0 to 59) */
  int8_t         gmt_offset;  /**< Offset related to GMT, by 15-min interval (from -48 to +52) */
} __attribute__((__packed__)) iso9660_date_t;


/**
 *  Directory record file flags bitfield
 */
typedef struct iso9660_dr_flags_s {
  int   existence   : 1;
  int   directory   : 1;
  int   associated  : 1;
  int   record      : 1;
  int   protection  : 1;
  int     __reserved: 2;
  int   multiextent : 1;
} __attribute__((__packed__)) iso9660_dr_flags_t;

#define ISO9660_RECORD_FLAG_EXISTENCE     (1 << 0)
#define ISO9660_RECORD_FLAG_DIRECTORY     (1 << 1)
#define ISO9660_RECORD_FLAG_ASSOCIATED    (1 << 2)
#define ISO9660_RECORD_FLAG_RECORD        (1 << 3)
#define ISO9660_RECORD_FLAG_PROTECTION    (1 << 4)
#define ISO9660_RECORD_FLAG_MULTIEXTENT   (1 << 7)

/**
 *  ISO9660 directory record
 *
 *   The directory record is immediately followed by the file identifier.
 */
typedef uint8_t iso9660_directory_record_t;

#define ISO9660_RECORD_LEN(_x)                ISO_U8(_x, 0)
#define ISO9660_RECORD_EX_ATTR_RECORD_LEN(_x) ISO_U8(_x, 1)
#define ISO9660_RECORD_LOCATION(_x)           ISO_BU32(_x, 2)
#define ISO9660_RECORD_DATA_LENGTH(_x)        ISO_BU32(_x, 10)
#define ISO9660_RECORD_DATE(_x)               ISO_PTR(_x, 18, iso9660_date_t)
#define ISO9660_RECORD_FLAGS(_x)              ISO_U8(_x, 25)
#define ISO9660_RECORD_UNIT_SIZE(_x)          ISO_U8(_x, 26)
#define ISO9660_RECORD_GAP_SIZE(_x)           ISO_U8(_x, 27)
#define ISO9660_RECORD_SEQ_NUMBER(_x)         ISO_BU16(_x, 28)
#define ISO9660_RECORD_LEN_FI(_x)             ISO_U8(_x, 32)
#define ISO9660_RECORD_FILE_IDENTIFIER(_x)    ISO_STR(_x, 33)
#define ISO9660_RECORD_SYSTEM_USE_OFS(_x)     (33                                  \
                                                + ISO9660_RECORD_LEN_FI(_x)         \
                                                + (1 - ISO9660_RECORD_LEN_FI(_x)%2))
#define ISO9660_RECORD_SYSTEM_USE(_x)         ISO_PTR(_x,                         \
                                                ISO9660_RECORD_SYSTEM_USE_OFS(_x), \
                                                void)
#define ISO9660_RECORD_SYSTEM_USE_LEN(_x)     (ISO9660_RECORD_LEN(_x)             \
                                                - ISO9660_RECORD_SYSTEM_USE_OFS(_x))


/**
 *  ISO9660 path table record
 *
 *   The directory record is immediately followed by the directory identifier.
 */
typedef struct iso9660_path_table_record_s {
  uint8_t               len_di;         /**< Length of the directory identifier */
  uint8_t               ex_attr_length; /**< Length of the extended attribute record */
  uint32_t              location;       /**< Extent location TODO: BE? LE? */
  uint16_t              parent_number;  /**< Parent directory number TODO: BE? LE? */
} __attribute__((__packed__)) iso9660_path_table_record_t;


/**
 *  ISO9660 extended attribute record
 *
 *   The directory record is immediately followed by len_au bytes reserved
 * to the application and then len_esc btes of escape sequences which designate
 * the coded character sets.
 */
typedef struct iso9660_ex_attr_record_s {
  iso9660_numeric16_t   owner;
  iso9660_numeric16_t   group;
  uint16_t              permissions;
  iso9660_bcd_date_t    creation_date;
  iso9660_bcd_date_t    modification_date;
  iso9660_bcd_date_t    expiration_date;
  iso9660_bcd_date_t    effective_date;
  uint8_t               format;
  uint8_t               attributes;
  iso9660_numeric16_t   length;
  char                  system_id[32];
  char                  user[64];
  uint8_t               version;
  uint8_t               len_esc;
  uint8_t                   unused[64];
  uint8_t               len_au;
} __attribute__((__packed__)) iso9660_ex_attr_record_t;


typedef char  iso9660_descriptor_t;

/**
 *  ISO9660 general representation of a volume descriptor
 */
#define ISO9660_VOLUME_DESC_TYPE(_x)            ISO_U8(_x, 0)
#define ISO9660_SET_VOLUME_DESC_TYPE(_x, _v)    ISO_SET_U8(_x, 0, _v)
#define ISO9660_VOLUME_DESC_ID(_x)              ISO_STR(_x, 1)
#define ISO9660_VOLUME_DESC_VERSION(_x)         ISO_U8(_x, 6)

/**
 *  ISO9660 primary volume descriptor
 */
#define ISO9660_PVD_SYSTEM_ID(_x)               ISO_STR(_x, 8)
#define ISO9660_PVD_SYSTEM_ID_LEN               32
#define ISO9660_PVD_VOLUME_ID(_x)               ISO_STR(_x, 40)
#define ISO9660_PVD_VOLUME_ID_LEN               32
#define ISO9660_PVD_VOLUME_SPACE_SIZE(_x)       ISO_BU32(_x, 80)
#define ISO9660_PVD_VOLUME_SET_SIZE(_x)         ISO_BU16(_x, 120)
#define ISO9660_PVD_VOLUME_SEQ_NUMBER(_x)       ISO_BU16(_x, 124)
#define ISO9660_PVD_LOGICAL_BLOCK_SIZE(_x)      ISO_BU16(_x, 128)
#define ISO9660_PVD_PATH_TABLE_SIZE(_x)         ISO_BU32(_x, 132)
#define ISO9660_PVD_L_PATH_TABLE(_x)            ISO_BU16(_x, 140)
#define ISO9660_PVD_L_PATH_TABLE2(_x)           ISO_BU16(_x, 144)
#define ISO9660_PVD_M_PATH_TABLE(_x)            ISO_BU16(_x, 148)
#define ISO9660_PVD_M_PATH_TABLE2(_x)           ISO_BU16(_x, 152)
#define ISO9660_PVD_ROOT_DIRECTORY_RECORD(_x)   ISO_PTR(_x, 156, iso9660_directory_record_t)
#define ISO9660_PVD_CREATION_DATE(_x)           ISO_PTR(_x, 813, iso9660_bcd_date_t)
#define ISO9660_PVD_MODIFICATION_DATE(_x)       ISO_PTR(_x, 830, iso9660_bcd_date_t)
#define ISO9660_PVD_EXPIRATION_DATE(_x)         ISO_PTR(_x, 847, iso9660_bcd_date_t)
#define ISO9660_PVD_EFFECTIVE_DATE(_x)          ISO_PTR(_x, 864, iso9660_bcd_date_t)
#define ISO9660_PVD_FILE_STRUCTURE_VERSION(_x)  (uint8_t *)((_x) + 881)


/**
 *  ISO9660 supplementary volume descriptor
 */

#define ISO9660_SVD_VOLUME_FLAGS(_x)            ISO_U8(_x, 7)
#define ISO9660_SVD_ESCAPE_SEQUENCES(_x)        ISO_STR(_x, 88)
#define ISO9660_SVD_ESCAPE_SEQUENCES_LEN        32

/**
 *  SVD volume flags
 */
#define ISO9660_NON_STANDARD_ESCAPE_SEQUENCE    (1 << 0)


/** Type describing the actual and/or desired ISO9660 volume version */
typedef uint8_t iso9660_version_t;

/**
 *   Initialization of the ISO9660 volume descriptor
 *
 * @param mt_entry  Mount table entry
 */
int
iso9660_init_volume_info(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_version_t                        version
);


/**
 *   Read the various descriptors in the iso9660 volume descriptor set
 * and determine which volume descriptor is the most "powerful" according
 * to the given supported version.
 *
 *   The fallback descriptor is always the PVD which shall be present in any
 * case. If that descriptor is not found it's a serious error of the volume.
 *
 * @param mt_entry    Mount table entry
 * @param superblock  Pointer to a buffer to be filled with the determined
 *                    descriptor, it could be a PVD, an SVD or an EVD.
 * @param version     Supported ISO9660 version
 *
 * @return 0 if the buffer contains the valid superblock, -1 if an error occured
 */
int
iso9660_read_superblock(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_descriptor_t                    *superblock,
    iso9660_version_t                        version
);


/**
 *   Read the specified descriptor from the ISO9660 volume descriptor set.
 * The sector is numbered relatively to the first descriptor, located after
 * the first ISO9660_SYSTEM_USE_SECTORS reserved sectors.
 *
 * @param fs          A pointer to the iso9660 filesystem information structure
 * @param descriptor  The output buffer
 * @param sector      Sector number relative to the first descriptor sector
 *
 * @return 0 if the descriptor is successfully read, -1 if an error occured
 */
int
iso9660_read_descriptor(
    rtems_filesystem_mount_table_entry_t    *mt_entry,
    iso9660_descriptor_t                    *descriptor,
    iso9660_sector_t                         sector
);


/**
 *   Check the ISO9660 descriptor structure validity. Every descriptor
 * can be cast-ed to an iso9660_descriptor_t.
 *
 * @param descriptor Pointer to the 2048 byte sector buffer of the descriptor
 */
int
iso9660_check_descriptor(
    iso9660_descriptor_t  *descriptor
);


/**
 *   Update the given version according to the descriptor data.
 *
 * @param descriptor      Pointer to the 2048 byte sector buffer of the
 *                        descriptor
 * @param volume_version  Pointer to an iso9660_version_t to be completed
 *                        according to this descriptor data.
 *
 * @return 0 if the update is OK
 */
int
iso9660_update_version(
    iso9660_descriptor_t     *descriptor,
    iso9660_version_t        *volume_version
);

/** ISO9660 default sector size */
#define ISO9660_SECTOR_SIZE             2048
#define ISO9660_SYSTEM_USE_SECTORS      16

/** ISO9660 multi-endian data access */
#if BYTE_ORDER == LITTLE_ENDIAN
#define iso9660_numeric(_n) (_n.le)
#elif BYTE_ORDER == BIG_ENDIAN
#define iso9660_numeric(_n) (_n.be)
#else
#error "Unable to determine platform endianness, you should manually define BYTE_ORDER."
#endif

/** Mask of the standard part in an ISO9660 version */
#define ISO9660_VERSION_STANDARD_MASK   (0x03)
/** Mask of the extension part in an ISO9660 version */
#define ISO9660_VERSION_EXTENSION_MASK  (0xF4)

#define ISO9660_STANDARD_1988           (0x01)  /**< ISO9660 / ECMA-119 standard */
#define ISO9660_STANDARD_1999           (0x02)  /**< ISO9660:1999 standard */
#define ISO9660_STANDARD_ANY            ( ISO9660_STANDARD_1988             \
                                         | ISO9660_STANDARD_1999 )

#define ISO9660_EXTENSION_SUSP          (1 << 2)
#define ISO9660_EXTENSION_ROCK_RIDGE    (1 << 3)
#define ISO9660_EXTENSION_JOLIET_LEVEL1 (1 << 4)
#define ISO9660_EXTENSION_JOLIET_LEVEL2 (1 << 5)
#define ISO9660_EXTENSION_JOLIET_LEVEL3 (1 << 6)
#define ISO9660_EXTENSION_JOLIET        ( ISO9660_EXTENSION_JOLIET_LEVEL1   \
                                         | ISO9660_EXTENSION_JOLIET_LEVEL2   \
                                         | ISO9660_EXTENSION_JOLIET_LEVEL3)

#define ISO9660_EXTENSION_ALL           ( ISO9660_EXTENSION_SUSP             \
                                         | ISO9660_EXTENSION_ROCK_RIDGE       \
                                         | ISO9660_EXTENSION_JOLIET )

#define iso9660_version_supports(_v, _t) ((_v) & (_t))
#ifdef __cplusplus
}
#endif

#endif /* __ISO9660FS_H_ */
